#include "p2pms_util.hpp"

namespace p2pms 
{

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void SyncProxy(rtc::Thread* thread, std::function<void()> func) {
	if (thread->IsCurrent()) {
		func();
		return;
	}

	rtc::Event event;
	thread->PostTask([&]() {
		func();
		event.Set();
	});
	event.Wait(rtc::Event::kForever);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
std::string GenerateRandomStr(size_t length) {
	static const char charset[] =
		"0123456789"
		"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
		"abcdefghijklmnopqrstuvwxyz";
	static thread_local std::mt19937 rg{ std::random_device{}() };
	static thread_local std::uniform_int_distribution<size_t> pick(0, sizeof(charset) - 2);

	std::string s;
	s.reserve(length);
	for (size_t i = 0; i < length; ++i)
		s += charset[pick(rg)];
	return s;
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
std::string GenerateUUID() {
	static std::random_device rd;
	static std::mt19937 gen(rd());
	static std::uniform_int_distribution<> dis(0, 15);
	static std::uniform_int_distribution<> dis2(8, 11);

	std::stringstream ss;
	int i;
	ss << std::hex;
	for (i = 0; i < 8; i++) ss << dis(gen);
	ss << "-";
	for (i = 0; i < 4; i++) ss << dis(gen);
	ss << "-4";
	for (i = 0; i < 3; i++) ss << dis(gen);
	ss << "-";
	ss << dis2(gen);
	for (i = 0; i < 3; i++) ss << dis(gen);
	ss << "-";
	for (i = 0; i < 12; i++) ss << dis(gen);
	return ss.str();
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
std::string ToString(const MediaInfo& media)
{
	return "MediaInfo{media_id: " + media.media_id + 
		", media_type: " + media.media_type + 
		", media_name: " + media.media_name + 
		", src_id: " + media.src_id + 
		", src_type: " + media.src_type + 
		", src_name: " + media.src_name + "}";
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
 std::string ToString(CallState state)
 {
 	switch (state) {
 	case CallState::Idle:
 		return "Idle";
 	case CallState::Calling:
 		return "Calling";
 	case CallState::Connected:
 		return "Connected";
 	case CallState::Terminating:
 		return "Terminating";
 	case CallState::Terminated:
 		return "Terminated";
 	default:
 		return "Unknown";
 	}
 }

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
bool ValidateMediaInfo(const MediaInfo& media)
{
	if (media.media_id.empty() || media.media_type.empty() || 
		media.media_name.empty() || media.src_id.empty() || 
		media.src_type.empty() || media.src_name.empty()) {
		return false;
	}
	return true;
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
bool CompareMediaInfo(const MediaInfo& lhs, const MediaInfo& rhs)
{
	return lhs.media_id == rhs.media_id &&
		   lhs.media_type == rhs.media_type &&
		   lhs.media_name == rhs.media_name &&
		   lhs.src_id == rhs.src_id &&
		   lhs.src_type == rhs.src_type &&
		   lhs.src_name == rhs.src_name;
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
bool IsP2PCall(CallMode mode)
{
	return mode == CallMode::CALL_MODE_P2P || mode == CallMode::CALL_MODE_P2SP;
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeCommonResMsg(const std::string& cmd,const std::string& rid, int code, 
	const std::string& msg, const nlohmann::json& data)
{
	return Message{ cmd, rid, ResMsgData{code, msg, data} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeLoginReqMsg(const std::string& user_id)
{
	return Message{ LOGIN_REQ, GenerateRandomStr(), LoginReqData{user_id} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeLoginAckMsg(const std::string& rid, const std::string& user_id)
{
	return Message{ LOGIN_ACK, rid, ResMsgData{0, "success", LoginAckData{user_id}} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeCallMakeReqMsg(const std::string& call_type, const std::string& caller_id, 
	const std::string& callee_id)
{
	return Message{ CALL_MAKE_REQ, GenerateRandomStr(), 
		CallMakeReqData{call_type, caller_id, callee_id} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeCallMakeAckMsg(const std::string& rid, int code, const std::string& msg, 
	const std::string& call_type, const std::string& call_id, const std::string& caller_id,
	const std::string& callee_id)
{
	return Message{ CALL_MAKE_ACK, rid, 
		ResMsgData{code, msg, CallMakeAckData{call_type, call_id, caller_id, callee_id}} };	
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeP2PCandidateNotifyMsg(const std::string& call_id, const std::string& user_id, 
	const std::string& sdp_mid, int sdp_mline_index, const std::string& candidate)
{
	return Message{ P2P_CANDIDATE_NOTIFY, GenerateRandomStr(), 
		P2PCandidateNotifyData{call_id, user_id, sdp_mid, sdp_mline_index, candidate} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeP2PNegotiateReqMsg(const std::string& call_id, const std::string& user_id, 
	const std::string& sdp)
{
	return Message{ P2P_NEGOTIATE_REQ, GenerateRandomStr(), 
		P2PNegotiateReqData{call_id, user_id, sdp} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeP2PNegotiateResMsg(const std::string& rid, int code, const std::string& msg, 
	const std::string& call_id, const std::string& user_id, const std::string& sdp)
{
	if (code == 0) {
		return Message{ P2P_NEGOTIATE_RES, rid, 
			ResMsgData{code, msg, P2PNegotiateResData{call_id, user_id, sdp}} };
	} else {
		return Message{ P2P_NEGOTIATE_RES, rid, ResMsgData{code, msg, nlohmann::json{}} };
	}
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeCallEndReqMsg(const std::string& call_id, const std::string& user_id)
{
	return Message{ CALL_END_REQ, GenerateRandomStr(), CallEndReqData{call_id, user_id} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeCallEndAckMsg(const std::string& rid, int code, const std::string& msg, 
	const std::string& call_id, const std::string& user_id)
{
	return Message{ CALL_END_ACK, rid, ResMsgData{code, msg, CallEndAckData{call_id, user_id}} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeP2PStartPublishReqMsg(const std::string& call_id, const std::string& user_id,
	const MediaInfo& media)
{
	return Message{ P2P_START_PUBLISH_REQ, GenerateRandomStr(), 
		P2PStartPublishReqData{call_id, user_id, media} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeP2PStopPublishReqMsg(const std::string& call_id, const std::string& user_id,
	const MediaInfo& media)
{
	return Message{ P2P_STOP_PUBLISH_REQ, GenerateRandomStr(),
		P2PStopPublishReqData{call_id, user_id, media} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeP2PStopPublishAckMsg(const std::string& rid, int code, const std::string& msg,
	const std::string& call_id, const std::string& user_id, const MediaInfo& media)
{
	return Message{ P2P_STOP_PUBLISH_ACK, rid, 
		ResMsgData{code, msg, P2PStopPublishAckData{call_id, user_id, media}} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeP2PStartSubscribeReqMsg(const std::string& call_id, const std::string& user_id,
	const std::string& pub_user_id, const MediaInfo& media)
{
	return Message{ P2P_START_SUBSCRIBE_REQ, GenerateRandomStr(), 
		P2PStartSubscribeReqData{call_id, user_id, pub_user_id, media} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeP2PStopSubscribeReqMsg(const std::string& call_id, const std::string& user_id,
	const std::string& pub_user_id, const MediaInfo& media)
{
	return Message{ P2P_STOP_SUBSCRIBE_REQ, GenerateRandomStr(),
		P2PStopSubscribeReqData{call_id, user_id, pub_user_id, media} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeP2PStartProduceAckMsg(const std::string& rid, int code, const std::string& msg, 
	const std::string& call_id, const std::string& user_id)
{
	return Message{ P2P_START_PRODUCE_ACK, rid, 
		ResMsgData{code, msg, P2PStartProduceAckData{call_id, user_id}} };
}

//--------------------------------------------------------------------------------------------------
// SFU Message Constructors
//--------------------------------------------------------------------------------------------------
Message MakeSFUPrepareRoomReqMsg(const std::string& user_id, const std::string& room_id)
{
	return Message{ SFU_PREPARE_ROOM_REQ, GenerateRandomStr(), 
		SFUPrepareRoomReqData{user_id, room_id} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeSFUJoinRoomReqMsg(const std::string& room_id, const std::string& user_id,
	const nlohmann::json& rtp_caps, const nlohmann::json& sctp_caps)
{
	return Message{ SFU_JOIN_ROOM_REQ, GenerateRandomStr(), 
		SFUJoinRoomReqData{room_id, user_id, rtp_caps, sctp_caps} };
}

Message MakeSFULeaveRoomReqMsg(const std::string& room_id, const std::string& user_id)
{
	return Message{ SFU_LEAVE_ROOM_REQ, GenerateRandomStr(), 
		SFULeaveRoomReqData{room_id, user_id} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeSFUCreateTransportReqMsg(const std::string& room_id, const std::string& user_id,
	const std::string& transport_type, bool force_tcp, bool producing, bool consuming, 
	const nlohmann::json& sctp_caps)
{
	return Message{ SFU_CREATE_TRANSPORT_REQ, GenerateRandomStr(), SFUCreateTransportReqData{
		room_id, user_id, transport_type, force_tcp, producing, consuming, sctp_caps} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeSFUConnectTransportReqMsg(const std::string& room_id, const std::string& user_id, 
	const std::string& transport_type, const std::string& transport_id, 
	const std::string& send_or_recv, const nlohmann::json& dtls_params)
{
	return Message{ SFU_CONNECT_TRANSPORT_REQ, GenerateRandomStr(), 
		SFUConnectTransportReqData{
			room_id, user_id, transport_type, transport_id, send_or_recv, dtls_params} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeSFUStartProduceReqMsg(const std::string& room_id, const std::string& user_id, 
	const MediaInfo& media, const std::string& transport_id, const std::string& kind,
	const nlohmann::json& rtp_params, const nlohmann::json& app_data)
{
	return Message{ SFU_START_PRODUCE_REQ, GenerateRandomStr(), 
		SFUStartProduceReqData{room_id, user_id, media, transport_id, kind, rtp_params, app_data} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeSFUStopProduceReqMsg(const std::string& room_id, const std::string& user_id,
	const MediaInfo& media, const std::string& producer_id)
{
	return Message{ SFU_STOP_PRODUCE_REQ, GenerateRandomStr(), 
		SFUStopProduceReqData{room_id, user_id, media, producer_id} };	
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeSFUStartPublishReqMsg(const std::string& room_id, const std::string& user_id,
	const MediaInfo& media)
{
	return Message{ SFU_START_PUBLISH_REQ, GenerateRandomStr(), 
		SFUStartPublishReqData{room_id, user_id, media} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeSFUStartPublishAckMsg(const std::string& rid, const std::string& room_id, 
	const std::string& user_id, const MediaInfo& media)
{
	return Message{ SFU_START_PUBLISH_ACK, rid, 
		ResMsgData{0, "success", SFUStartPublishAckData{room_id, user_id, media}} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeSFUStopPublishAckMsg(const std::string& rid, const std::string& room_id,
	const std::string& user_id, const MediaInfo& media)
{
	return Message{ SFU_STOP_PUBLISH_ACK, rid,
		ResMsgData{0, "success", SFUStopPublishAckData{room_id, user_id, media}} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeSFUStartSubscribeReqMsg(const std::string& room_id, const std::string& sub_user_id,
	const std::string& pub_user_id, const MediaInfo& media)
{
	return Message{ SFU_START_SUBSCRIBE_REQ, GenerateRandomStr(), 
		SFUStartSubscribeReqData{room_id, sub_user_id, pub_user_id, media} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeSFUStopPublishReqMsg(const std::string& room_id, const std::string& user_id,
	const MediaInfo& media)
{
	return Message{ SFU_STOP_PUBLISH_REQ, GenerateRandomStr(), 
		SFUStopPublishReqData{room_id, user_id, media} };
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
Message MakeSFUStopSubscribeReqMsg(const std::string& room_id, const std::string& user_id,
	const MediaInfo& media)
{
	return Message{ SFU_STOP_SUBSCRIBE_REQ, GenerateRandomStr(), 
		SFUStopSubscribeReqData{room_id, user_id, media} };
}

}